<?php

namespace DeCMF\Core\Db;

use Think\Db;

/**
 * 实现通过连写的方式构造SQL查询语句
 */
class Query
{
    /**
     *
     * @var array 查询表达式参数
     */
    protected $options   = [];
    
    /**
     *
     * @var \DeCMF\Core\Db\Model 
     */
    protected $model = null;

    /**
     *
     * @var string|null null代表以系统前缀为准;如果不使用系统前缀,则传字符串
     */
    protected $tablePrefix = null;
    
    /**
     *
     * @var \Think\Db\Driver 当前数据库操作对象
     */
    protected $db = null;

    /**
     * @param \DeCMF\Core\Db\Model $model 模型对象
     * @param string|null $prefix 表名前缀
     */
    public function __construct($model = null, $prefix = null)
    {
        if (is_string($prefix)) {
            $this->tablePrefix = $prefix;
        }
        
        if ($model) {
            $this->model = $model;
        }
        
    }
    
    /**
     * 利用__call方法实现一些特殊的方法 如getByXXXXX  getFieldByXXXX
     * @param string $method 方法名称
     * @param array $args 调用参数
     * @return mixed
     */
    public function __call($method, $args)
    {
        if (strtolower(substr($method, 0, 5)) == 'getby') {
            // 根据某个字段获取记录
            $field = parse_name(substr($method, 5));
            $where[$field] = $args[0];
            return $this->where($where)->find();
        } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') {
            // 根据某个字段获取记录的某个值
            $name = parse_name(substr($method, 10));
            $where[$name] = $args[0];
            return $this->where($where)->getField($args[1]);
        } else {
            E(__CLASS__ . ':' . $method . L('_METHOD_NOT_EXIST_'));
            return;
        }
    }
    /**
     *
     * @var array 数据库对象池
     */
    private $conns = [];
    
    /**
     * 切换当前的数据库连接
     * @param integer $linkNum  连接序号
     * @param mixed $config  数据库连接信息 为null代表关闭链接
     * @param boolean $force 强制重新连接
     * @return \Think\Db\Driver|null
     */
    public function db($linkNum = '', $config = '', $force = false)
    {
        if ('' === $linkNum && $this->db) {
            return $this->db;
        }

        if (!isset($this->conns[$linkNum]) || $force) {
            // 创建一个新的实例
            if (!empty($config) && is_string($config) && false === strpos($config, '/')) {
                // 支持读取配置参数
                $config = C($config);
            }
            $this->conns[$linkNum] = Db::getInstance($config);
        } elseif (null === $config) {
            $this->conns[$linkNum]->close(); // 关闭数据库连接
            unset($this->conns[$linkNum]);
            return;
        }

        // 切换数据库连接
        $this->db = $this->conns[$linkNum];
        $this->_after_db();

        return $this->db;
    }
    
    /**
     * 数据库切换后回调方法
     */
    protected function _after_db()
    {}
    
    /**
     * 查询数据
     * 
     * @return mixed
     */
    public function find()
    {
        // 分析表达式
        $options = $this->parseOptions(['limit' => 1]);
        // 判断查询缓存
        if (isset($options['cache'])) {
            $cache = $options['cache'];
            $key   = is_string($cache['key']) ? $cache['key'] : md5(serialize($options));
            $data  = S($key, '', $cache);
            if (false !== $data) {
                if ($this->model) {
                    $this->model->data($data);
                }
                return $data;
            }
        }
        $resultSet = $this->db->select($options);
        if (false === $resultSet) {
            return false;
        }
        if (empty($resultSet)) {
            // 查询结果为空
            return null;
        }
        if (is_string($resultSet)) {
            return $resultSet;
        }

        // 读取数据后的处理
        if ($this->model) {
            $data = $this->model->_read_data($resultSet[0]);
            $this->model->afterFind($data, $options);
            if (!empty($this->options['result'])) {
                return $this->model->returnResult($data, $this->options['result']);
            }
            $this->model->data($data);
        }
        
        if (isset($cache)) {
            S($key, $data, $cache);
        }
        return $data;
    }
    
    /**
     * 删除数据 如果条件为空 不进行删除操作 除非设置 1=1
     * 
     * @return mixed
     */
    public function delete()
    {
        // 分析表达式
        $options = $this->parseOptions();
        if (empty($options['where'])) {
            return false;
        }
        
        if ($this->model && 
            false === $this->model->beforeDelete($options)) {
            return false;
        }
        
        $result = $this->db->delete($options);
        
        // 返回删除记录个数
        return $result;
    }
    
    /**
     * 查询数据集
     * 
     * @param array $options 表达式参数
     * @return mixed
     */
    public function select()
    {
        // 分析表达式
        $options = $this->parseOptions();
        // 判断查询缓存
        if (isset($options['cache'])) {
            $cache = $options['cache'];
            $key   = is_string($cache['key']) ? $cache['key'] : md5(serialize($options));
            $data  = S($key, '', $cache);
            if (false !== $data) {
                return $data;
            }
        }
        $resultSet = $this->db->select($options);
        if (false === $resultSet) {
            return false;
        }
        if (!empty($resultSet)) {
            // 有查询结果
            if (is_string($resultSet)) {
                return $resultSet;
            }
            
            if ($this->model) {
                $this->model->afterSelect($resultSet, $options);
            }
        }

        if (isset($cache)) {
            S($key, $resultSet, $cache);
        }
        return $resultSet;
    }
    
    /**
     * 新增数据
     * 
     * @param mixed $data 数据
     * @param boolean $replace 是否replace
     * @return mixed
     */
    public function insert($data = '', $replace = false)
    {
        // 分析表达式
        $options = $this->parseOptions();
        if ($this->model && false === $this->model->beforeInsert($data, $options)) {
            return false;
        }
        // 写入数据到数据库
        $result = $this->db->insert($data, $options, $replace);
        
        if ($this->model) {
            return $this->model->afterInsert($result, $options);
        }
        
        return $result;
    }
    
    /**
     * 保存数据
     * 
     * @param mixed $data 数据
     * @return boolean
     */
    public function update($data = '')
    {
        // 分析表达式
        $options = $this->parseOptions();
        
        if ($this->model && (false === $this->model->beforeUpdate($options, $data))) {
            return false;
        }
        
        $result = $this->db->update($data, $options);
        
        return $result;
    }
    
    /**
     * 指定当前的数据表,会匹配 __表名(两边双下划线)__ 加上表名前缀
     * @TODO 可以根据实际情况看看是否有更好的处理表名前缀的方案
     * 
     * @param array|string $table TP原来的逻辑:数组时,不会替换表名前缀
     * @return $this
     * @throws \Think\Exception 指定空表名会引发异常
     */
    public function table($table)
    {
        if (is_array($table)) {
            $this->options['table'] = $table;
        } elseif (!empty($table)) {
            $this->options['table'] = $this->replaceWithRawTableName($table);
        } else {
            E(__CLASS__ . ':' . L('_TABLE_NAME_INVALID_'));
        }
        return $this;
    }
    
    /**
     * 指定查询字段 支持字段排除
     * 
     * @param string $field
     * @return $this
     */
    public function field($field)
    {
        $this->options['field'] = $field;
        
        return $this;
    }
    
    /**
     * 指定查询条件 支持安全过滤
     * 
     * @param array|string|object $where 条件表达式
     * @param mixed $parse 预处理参数
     * @return $this
     */
    public function where($where)
    {
        if (is_object($where)) {
            $where = get_object_vars($where);
        }
        if (is_string($where) && '' != $where) {
            $where = ['_string' => $where];
        }
        if (isset($this->options['where'])) {
            $this->options['where'] = array_merge($this->options['where'], $where);
        } else {
            $this->options['where'] = $where;
        }

        return $this;
    }

    /**
     * 指定查询数量
     * 
     * @param mixed $offset 起始位置
     * @param mixed $length 查询数量
     * @return $this
     */
    public function limit($offset, $length = null)
    {
        if (is_null($length) && strpos($offset, ',')) {
            list($offset, $length) = explode(',', $offset);
        }
        $this->options['limit'] = intval($offset) . ($length ? ',' . intval($length) : '');
        return $this;
    }

    /**
     * 指定分页
     * 
     * @param mixed $page 页数
     * @param mixed $listRows 每页数量
     * @return $this
     */
    public function page($page, $listRows = null)
    {
        if (is_null($listRows) && strpos($page, ',')) {
            list($page, $listRows) = explode(',', $page);
        }
        $this->options['page'] = array(intval($page), intval($listRows));
        return $this;
    }

    /**
     * 查询注释
     * 
     * @param string $comment 注释
     * @return $this
     */
    public function comment($comment)
    {
        $this->options['comment'] = $comment;
        return $this;
    }

    /**
     * 获取执行的SQL语句
     * 
     * @param boolean $fetch 是否返回sql
     * @return $this
     */
    public function fetchSql($fetch = true)
    {
        $this->options['fetch_sql'] = $fetch;
        return $this;
    }
    
    /**
     * 查询SQL组装 join,表名前缀由getRawTableName处理
     * 
     * @param string|array $join
     * @param string $type JOIN类型
     * @return $this
     */
    public function join($join, $type = 'INNER')
    {
        if (!$join || (!is_array($join) && !is_string($join))) {
            E(__CLASS__ . ':join' . L('_JOIN_INVALID_'));
        }
        
        if (is_string($join)) {
            $join = [$join];
        }
        
        foreach ($join as $_join) {
            $_join = $this->replaceWithRawTableName($_join);
            
            $_join = false !== stripos($_join, 'JOIN') 
                    ? $_join 
                    : $type . ' JOIN ' . $_join;
            $this->options['join'][] = $_join;
        }
        
            
        return $this;
    }

    /**
     * 查询SQL组装 union
     * 
     * @param mixed $union
     * @param boolean $all
     * @return $this
     */
    public function union($union, $all = false)
    {
        if (empty($union) 
           || (!is_array($union) && !is_string($union) && !is_object($union))) {
            E(L('_DATA_TYPE_INVALID_'));
        }

        if ($all) {
            $this->options['union']['_all'] = true;
        }
        if (is_object($union)) {
            $union = get_object_vars($union);
        }
        // 转换union表达式
        if (is_string($union)) {
            $union = [$union];
        }
        
        foreach ($union as $key => $_union) {
            $union[$key] = $this->replaceWithRawTableName($_union);
        }
        
        $this->options['union'] = array_merge($this->options['union'], $union);
        
        return $this;
    }

    /**
     * USING支持 用于多表删除
     * 
     * @param string|array $using
     * @return $this
     */
    public function using($using)
    {
        if (empty($using) 
           || (!is_array($using) && !is_string($using))) {
            E(L('_DATA_TYPE_INVALID_'));
        }
        
        if (is_array($using)) {
            $this->options['using'] = $using;
        } elseif (!empty($using)) {
            $this->options['using'] = $this->replaceWithRawTableName($using);
        }
        return $this;
    }
    
    /**
     * 参数绑定,支持动态参数
     * 
     * @param string $key  参数名
     * @param mixed $value  绑定的变量及绑定参数
     * @return $this
     */
    public function bind($key, $value = false)
    {
        if (is_array($key)) {
            $this->options['bind'] = $key;
        } else {
            $num = func_num_args();
            if ($num > 2) {
                $params = func_get_args();
                array_shift($params);
                $this->options['bind'][$key] = $params;
            } else {
                $this->options['bind'][$key] = $value;
            }
        }
        return $this;
    }

    /**
     * 查询缓存
     * 
     * @param mixed $key
     * @param integer $expire
     * @param string $type
     * @return $this
     */
    public function cache($key = true, $expire = null, $type = '')
    {
        // 增加快捷调用方式 cache(10) 等同于 cache(true, 10)
        if (is_numeric($key) && is_null($expire)) {
            $expire = $key;
            $key    = true;
        }
        if (false !== $key) {
            $this->options['cache'] = array('key' => $key, 'expire' => $expire, 'type' => $type);
        }

        return $this;
    }

    /**
     * 构造排序条件
     * 
     * @param string $value
     * @return $this
     */
    public function order($value)
    {
        $this->options['order'] = $value;
        return $this;
    }
    
    /**
     * 构造strict条件
     * 
     * @param string $value
     * @return $this
     */
    public function strict($value)
    {
        $this->options['strict'] = $value;
        return $this;
    }
    
    /**
     * 构造别名条件
     * 
     * @param string $value
     * @return $this
     */
    public function alias($value)
    {
        $this->options['alias'] = $value;
        return $this;
    }
    
    /**
     * 构造having条件
     * 
     * @param string $value
     * @return $this
     */
    public function having($value)
    {
        $this->options['having'] = $value;
        return $this;
    }
    
    /**
     * 构造group条件
     * 
     * @param string $value
     * @return $this
     */
    public function group($value)
    {
        $this->options['group'] = $value;
        return $this;
    }
    
    /**
     * 构造lock条件
     * 
     * @param string $value
     * @return $this
     */
    public function lock($value)
    {
        $this->options['lock'] = $value;
        return $this;
    }
    
    /**
     * 构造distinct条件
     * 
     * @param string $value
     * @return $this
     */
    public function distinct($value)
    {
        $this->options['distinct'] = $value;
        return $this;
    }
    
    /**
     * 构造filter条件
     * 
     * @param string $value
     * @return $this
     */
    public function filter($value)
    {
        $this->options['filter'] = $value;
        return $this;
    }
    
    /**
     * 构造result条件
     * 
     * @param string $value
     * @return $this
     */
    public function result($value)
    {
        $this->options['result'] = $value;
        return $this;
    }
    
    /**
     * 构造index条件
     * 
     * @param string $value
     * @return $this
     */
    public function index($value)
    {
        $this->options['index'] = $value;
        return $this;
    }
    
    /**
     * 构造force条件
     * 
     * @param string $value
     * @return $this
     */
    public function force($value)
    {
        $this->options['force'] = $value;
        return $this;
    }
    
    /**
     * 构造master条件
     * 
     * @param string $value
     * @return $this
     */
    public function master($value)
    {
        $this->options['master'] = $value;
        return $this;
    }
    
    /**
     * count统计
     * @param string $field
     * @return int
     */
    public function count($field = '')
    {
        $field = $field ?: '*';
        return $this->getField("COUNT({$field}) AS tp_count");
    }
    
    /**
     * sum查询
     * @param string $field
     * @return int
     */
    public function sum($field = '')
    {
        $field = $field ?: '*';
        return $this->getField("SUM({$field}) AS tp_sum");
    }
    
    /**
     * min查询
     * @param string $field
     * @return int
     */
    public function min($field = '')
    {
        $field = $field ?: '*';
        return $this->getField("MIN({$field}) AS tp_min");
    }
    
    /**
     * max查询
     * @param string $field
     * @return int
     */
    public function max($field = '')
    {
        $field = $field ?: '*';
        return $this->getField("MAX({$field}) AS tp_max");
    }
    
    /**
     * avg查询
     * @param string $field
     * @return int
     */
    public function avg($field = '')
    {
        $field = $field ?: '*';
        return $this->getField("AVG({$field}) AS tp_avg");
    }
    
    /**
     * 设置记录的某个字段值
     * 支持使用数据库字段和方法
     * 
     * @param string|array $field  字段名
     * @param string $value  字段值
     * @return boolean
     */
    public function setField($field, $value = '')
    {
        if (is_array($field)) {
            $data = $field;
        } else {
            $data[$field] = $value;
        }
        return $this->update($data);
    }

    /**
     * 字段值增长
     * 
     * @param string $field  字段名
     * @param integer $step  增长值
     * @param integer $lazyTime  延时时间(s)
     * @return boolean
     */
    public function setInc($field, $step = 1, $lazyTime = 0)
    {
        if ($this->model && ($lazyTime > 0)) {
            // 延迟写入
            $condition = $this->options['where'];
            $guid      = md5($this->model->getModelName() . '_' . $field . '_' . serialize($condition));
            $step      = $this->lazyWrite($guid, $step, $lazyTime);
            if (empty($step)) {
                return true; // 等待下次写入
            } elseif ($step < 0) {
                $step = '-' . $step;
            }
        }
        return $this->setField($field, array('exp', $field . '+' . $step));
    }

    /**
     * 字段值减少
     * 
     * @param string $field  字段名
     * @param integer $step  减少值
     * @param integer $lazyTime  延时时间(s)
     * @return boolean
     */
    public function setDec($field, $step = 1, $lazyTime = 0)
    {
        if ($this->model && ($lazyTime > 0)) {
            // 延迟写入
            $condition = $this->options['where'];
            $guid      = md5($this->model->getModelName() . '_' . $field . '_' . serialize($condition));
            $step      = $this->lazyWrite($guid, -$step, $lazyTime);
            if (empty($step)) {
                return true; // 等待下次写入
            } elseif ($step > 0) {
                $step = '-' . $step;
            }
        }
        return $this->setField($field, array('exp', $field . '-' . $step));
    }

    /**
     * 延时更新检查 返回false表示需要延时
     * 否则返回实际写入的数值
     * 
     * @param string $guid  写入标识
     * @param integer $step  写入步进值
     * @param integer $lazyTime  延时时间(s)
     * @return false|integer
     */
    protected function lazyWrite($guid, $step, $lazyTime)
    {
        if (false !== ($value = S($guid))) {
            // 存在缓存写入数据
            if (NOW_TIME > S($guid . '_time') + $lazyTime) {
                // 延时更新时间到了，删除缓存数据 并实际写入数据库
                S($guid, null);
                S($guid . '_time', null);
                return $value + $step;
            } else {
                // 追加数据到缓存
                S($guid, $value + $step, 0);
                return false;
            }
        } else {
            // 没有缓存数据
            S($guid, $step, 0);
            // 计时开始
            S($guid . '_time', NOW_TIME, 0);
            return false;
        }
    }

    /**
     * 获取一条记录的某个字段值
     * 
     * @param string $field  字段名
     * @param string $sepa  字段数据间隔符号 NULL返回数组
     * @return mixed
     */
    public function getField($field, $sepa = null)
    {
        $options['field'] = $field;
        $options = $this->parseOptions($options);
        // 判断查询缓存
        if (isset($options['cache'])) {
            $cache = $options['cache'];
            $key = is_string($cache['key']) ? $cache['key'] : md5($sepa . serialize($options));
            $data = S($key, '', $cache);
            if (false !== $data) {
                return $data;
            }
        }
        $field = trim($field);
        if (strpos($field, ',') && false !== $sepa) {
            // 多字段
            if (!isset($options['limit'])) {
                $options['limit'] = is_numeric($sepa) ? $sepa : '';
            }
            $resultSet = $this->db->select($options);
            if (!empty($resultSet)) {
                if (is_string($resultSet)) {
                    return $resultSet;
                }
                $_field = explode(',', $field);
                $field = array_keys($resultSet[0]);
                $key1 = array_shift($field);
                $key2 = array_shift($field);
                $cols = array();
                $count = count($_field);
                foreach ($resultSet as $result) {
                    $name = $result[$key1];
                    if (2 == $count) {
                        $cols[$name] = $result[$key2];
                    } else {
                        $cols[$name] = is_string($sepa) ? implode($sepa, array_slice($result, 1)) : $result;
                    }
                }
                if (isset($cache)) {
                    S($key, $cols, $cache);
                }
                return $cols;
            }
        } else {
            // 查找一条记录
            // 返回数据个数
            if (true !== $sepa) {
                // 当sepa指定为true的时候 返回所有数据
                $options['limit'] = is_numeric($sepa) ? $sepa : 1;
            }
            $result = $this->db->select($options);
            if (!empty($result)) {
                if (is_string($result)) {
                    return $result;
                }
                if (true !== $sepa && 1 == $options['limit']) {
                    $data = reset($result[0]);
                    if (isset($cache)) {
                        S($key, $data, $cache);
                    }
                    return $data;
                }
                foreach ($result as $val) {
                    $array[] = reset($val);
                }
                if (isset($cache)) {
                    S($key, $array, $cache);
                }
                return $array;
            }
        }
        return null;
    }
    
    public function addAll($dataList, $replace = false)
    {
        if (empty($dataList)) {
            return false;
        }
        // 数据处理
        foreach ($dataList as $key => $data) {
            $dataList[$key] = $this->facade($data);
        }
        // 分析表达式
        $options = $this->parseOptions($options);
        // 写入数据到数据库
        $result = $this->db->insertAll($dataList, $options, $replace);
        if (false !== $result) {
            $insertId = $this->getLastInsID();
            if ($insertId) {
                return $insertId;
            }
        }
        return $result;
    }

    /**
     * 通过Select方式添加记录
     * 
     * @param string $fields 要插入的数据表字段名
     * @param string $table 要插入的数据表名
     * @return boolean
     */
    public function selectAdd($fields = '', $table = '')
    {
        // 分析表达式
        $options = $this->parseOptions();
        // 写入数据到数据库
        $result = $this->db->selectInsert(
                $fields ?: $options['field'], 
                $table ?: $this->getTableName(), 
                $options
        );
        if (false === $result) {
            // 数据库插入操作失败
            return false;
        }
        
        // 插入成功
        return $result;
    }

    
    /**
     * 解析SQL语句
     * 
     * @param string $sql  SQL指令
     * @param boolean $parse  是否需要解析SQL
     * @return string
     */
    public function parseSql($sql, $parse)
    {
        // 分析表达式
        if (true === $parse) {
            $sql = $this->db->parseSql($sql, $this->parseOptions());
        } elseif (is_array($parse)) {
            // SQL预处理
            $parse = array_map(array($this->db, 'escapeString'), $parse);
            $sql = vsprintf($sql, $parse);
        } else {
            if ($this->model) {
                $sql = strtr(
                        $sql, 
                        ['__TABLE__' => $this->model->getTableName()]
                );
                $sql = $this->replaceWithRawTableName($sql);

                $this->db->setModel($this->model->getModelName());
            }
        }

        return $sql;
    }
    
    /**
     * 分析表达式,分析完后会清除options属性
     * 
     * @param array $options 表达式参数
     * @return array
     */
    protected function parseOptions(array $options = [])
    {
        $options = $options ? array_merge($this->options, $options) : $this->options;

        if (empty($options['table'])) {//从模型获取表名
            if (!$this->model) {
                E(L('__TABLE_OPTION_NOT_EXISTED'));
            }
            $options['table'] = $this->model->getTableName();
            $schema = $this->model->getTableSchema();
        } else {
            // 指定数据表 则重新获取字段列表 但不支持类型检测
            $schema = $this->getTableSchema($options['table']);
        }

        // 数据表别名
        if (!empty($options['alias'])) {
            $options['table'] .= ' ' . $options['alias'];
        }
        
        if ($this->model) {
            // 记录操作的模型名称
            $options['model'] = $this->model->getModelName();
        }

        // 字段类型验证
        if (isset($options['where']) && is_array($options['where']) && !empty($schema) && !isset($options['join'])) {
            // 对数组查询条件进行字段类型检查
            foreach ($options['where'] as $key => $val) {
                $key = trim($key);
                $fields = $schema;
                unset($fields['_pk'], $fields['_type']);
                if (in_array($key, $fields, true)) {
                    if (is_scalar($val)) {
                        $this->parseType($options['where'], $key, $schema['_type']);
                    }
                }
            }
        }
        // 查询过后清空sql表达式组装 避免影响下次查询
        $this->options = array();
        if ($this->model) {
            // 表达式过滤
            $this->model->_options_filter($options);
        }
        return $options;
    }
    
    /**
     * 构造完整的表名
     * @param string $str 可能带有表名的字符串
     * @return string
     */
    public function replaceWithRawTableName($str)
    {
        $prefix = is_string($this->tablePrefix) 
                ? $this->tablePrefix 
                : C('DB_PREFIX');
        $str = strtr($str, ['__PREFIX__' => $prefix]);
        return preg_replace_callback(
                "/__([A-Z0-9_-]+)__/sU", 
                function ($match) use ($prefix) {
                    return $prefix . strtolower($match[1]);
                }, 
                $str
        );
    }
    
    /**
     * 为实现解耦,增加从外部直接附加options的特性
     * @param array $options
     * @return $this
     */
    public function appendOptions($options)
    {
        if (!empty($options)) {
            $this->options = array_merge($this->options, $options);
        }
        
        return $this;
    }
    
    /**
     * 为实现解耦,增加从外部读取option项的特性
     * @return mixed
     */
    public function getOptions()
    {
        return $this->options;
    }
    
    /**
     * 为实现解耦,增加从外部读取option项的特性
     * @param string $key
     * @return mixed
     */
    public function getOption($key)
    {
        return isset($this->options[$key]) ? $this->options[$key] : null;
    }
    
    /**
     * 存储过程返回多数据集
     * 
     * @param string $sql  SQL指令
     * @param mixed $parse  是否需要解析SQL
     * @return array
     */
    public function procedure($sql, $parse = false)
    {
        return $this->db->procedure($sql, $parse);
    }

    /**
     * SQL查询
     * 
     * @param string $sql  SQL指令
     * @param mixed $parse  是否需要解析SQL
     * @return mixed
     */
    public function query($sql, $parse = false)
    {
        if (!is_bool($parse) && !is_array($parse)) {
            $parse = func_get_args();
            array_shift($parse);
        }
        $sql = $this->parseSql($sql, $parse);
        return $this->db->query($sql);
    }

    /**
     * 执行SQL语句
     * 
     * @param string $sql  SQL指令
     * @param mixed $parse  是否需要解析SQL
     * @return false | integer
     */
    public function execute($sql, $parse = false)
    {
        if (!is_bool($parse) && !is_array($parse)) {
            $parse = func_get_args();
            array_shift($parse);
        }
        $sql = $this->parseSql($sql, $parse);
        return $this->db->execute($sql);
    }
    
    /**
     * 启动事务
     * 
     * @return void
     */
    public function startTrans()
    {
        $this->commit();
        $this->db->startTrans();
        return;
    }

    /**
     * 提交事务
     * 
     * @return boolean
     */
    public function commit()
    {
        return $this->db->commit();
    }

    /**
     * 事务回滚
     * 
     * @return boolean
     */
    public function rollback()
    {
        return $this->db->rollback();
    }

    /**
     * 返回数据库的错误信息
     * 
     * @return string
     */
    public function getDbError()
    {
        return $this->db->getError();
    }

    /**
     * 返回最后插入的ID
     * 
     * @return string
     */
    public function getLastInsID()
    {
        return $this->db->getLastInsID();
    }

    /**
     * 返回最后执行的sql语句
     * 
     * @return string
     */
    public function getLastSql()
    {
        return $this->db->getLastSql($this->name);
    }
    
    /**
     * 对保存到数据库的数据进行处理
     * 先做字段类型casting 再用filter option过滤
     * 一般 add/update/addAll时用
     * 
     * @param array $data 要操作的数据
     * @param array $schema 可传DbModel fields属性过来,不传则不作字段类型校验
     * @return array
     */
    public function facade($data, $schema = [])
    {
        // 检查数据字段合法性
        if (!empty($schema)) {
            if (!empty($this->options['field'])) {
                $schema = $this->options['field'];
                unset($this->options['field']);
                if (is_string($schema)) {
                    $schema = explode(',', $schema);
                }
            }
            
            foreach ($data as $key => $val) {
                if (!in_array($key, $schema, true)) {
                    if (!empty($this->options['strict'])) {
                        E(L('_DATA_TYPE_INVALID_') . ':[' . $key . '=>' . $val . ']');
                    }
                    unset($data[$key]);
                } elseif (is_scalar($val) && !empty($schema['_type'])) {
                    // 字段类型检查 和 强制转换
                    $this->parseType($data, $key, $schema['_type']);
                }
            }
        }

        // 安全过滤
        if (!empty($this->options['filter'])) {
            $data = array_map($this->options['filter'], $data);
            unset($this->options['filter']);
        }
        
        return $data;
    }
    
    /**
     * 数据类型检测 根据参数绑定bind或field属性里的_type中的定义进行判断
     * 
     * @param mixed $data 数据
     * @param string $key 字段名
     * @param array $fieldSchemas 数据表对应的字段类型定义
     */
    protected function parseType(&$data, $key, $fieldSchemas)
    {
        if (!isset($this->options['bind'][':' . $key]) && isset($fieldSchemas[$key])) {
            $fieldType = strtolower($fieldSchemas[$key]);
            if (false !== strpos($fieldType, 'enum')) {
                // 支持ENUM类型优先检测
            } elseif (false === strpos($fieldType, 'bigint') && false !== strpos($fieldType, 'int')) {
                $data[$key] = intval($data[$key]);
            } elseif (false !== strpos($fieldType, 'float') || false !== strpos($fieldType, 'double')) {
                $data[$key] = floatval($data[$key]);
            } elseif (false !== strpos($fieldType, 'bool')) {
                $data[$key] = (bool) $data[$key];
            }
        }
    }
    
    /**
     * 获取表的字段Schema信息 带缓存策略
     * @param string $tableName
     * @return array 格式:
     * <pre>
     * [
     *      'id', 'username', 'email',
     *      '_pk' => 'id', // ['mp_id', 'openid'],
     *      '_type' => ['id' => 'INT', 'username' => 'VARCHAR', 'email' => 'VARCHAR']
     * ]
     * </pre>
     */
    public function getTableSchema($tableName)
    {
        $schema = [];
        
        if (C('DB_FIELDS_CACHE')) {
            $schema = F('_fields/' . strtolower($tableName));
            if ($schema) {
                return $schema;
            }
        }
        
        // 缓存不存在则查询数据表信息
        if ($this->model) {
            $this->db->setModel($this->model->getModelName());
        }
        
        $fields = $this->db->getFields($tableName);
        if (!$fields) {
            // 无法获取字段信息
            return $schema;
        }
        
        $schema = array_keys($fields);
        unset($schema['_pk']);
        foreach ($fields as $key => $val) {
            // 记录字段类型
            $type[$key] = $val['type'];
            
            // 增加复合主键支持
            if ($val['primary']) {
                if (isset($schema['_pk']) && null != $schema['_pk']) {
                    if (is_string($schema['_pk'])) {
                        $schema['_pk'] = array($schema['_pk']);
                    }
                } else {
                    $schema['_pk'] = $key;
                }
            }
        }
        // 记录字段类型信息
        $schema['_type'] = $type;

        // 2008-3-7 增加缓存开关控制
        if (C('DB_FIELDS_CACHE')) {
            // 永久缓存数据表信息
            F('_fields/' . strtolower($tableName), $schema);
        }
        
        return $schema;
    }
    
    /**
     * 根据table连写方法指定的table名取相关字段列表
     * @return array|boolean false代表失败
     */
    public function getDbFields()
    {
        if (isset($this->options['table'])) {
            // 动态指定表名
            if (is_array($this->options['table'])) {
                $table = key($this->options['table']);
            } else {
                $table = $this->options['table'];
                if (strpos($table, ')')) {
                    // 子查询
                    return false;
                }
            }
            $schema = $this->getTableSchema($table);
            unset($schema['_pk'], $schema['_type']);
            return $schema ? $schema : false;
        }
        
        return false;
    }
    
}

